﻿//Copyright (c) 2007 Microsoft Corporation. All rights reserved.
using System;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;

namespace Microsoft.Office.OneNote
{
    public abstract class OneNoteObject
    {
        private OneNoteObject _server;
        private OneNoteObjectFlags _flags;
        private OneNoteObjectProperties _properties;
        private OneNoteObjectStorageBase _innerObjects;

        /// <summary>
        /// Initializes a new instance of the <see cref="OneNoteObject"/> class.
        /// </summary>
        internal OneNoteObject()
        {
        }

        internal void SetInnerObjects(OneNoteObjectStorageBase innerObjects)
        {
            Contract.RequiresNotNull(innerObjects, "innerObjects");

            if (_innerObjects != null)
            {
                throw new InvalidOperationException();
            }

            _innerObjects = innerObjects;
        }

        // Conflicts

        /// <summary>
        /// Gets or sets a value indicating whether this instance has been changed in 
        /// OneNote since the last update.
        /// </summary>
        /// <remarks>
        /// <para>In order to determine what the new values are, call the <see cref="GetServerView"/> method.</para>
        /// <para>To acknowledge the local values and force overwriting of the OneNote data, call 
        /// the <see cref="M:AcceptChanges"/> method.
        /// </para>
        /// <para>To revert the local changes and load the updates from OneNote, 
        /// call the <see cref="M:RevertChanges"/> method.</para>
        /// </remarks>
        /// <value>
        /// 	<c>true</c> if this instance has conflicts; otherwise, <c>false</c>.
        /// </value>
        public bool HasConflicts
        {
            get 
            {
                CheckValidOrServerView(); 
                
                bool hasConflicts = CheckFlagsAnd(OneNoteObjectFlags.HasConflicts);

                if (_innerObjects != null)
                {
                    hasConflicts |= _innerObjects.HasConflicts;
                }

                return hasConflicts;
            }
            internal set { SetFlags(value, OneNoteObjectFlags.HasConflicts); }
        }

        public bool IsInvalid
        {
            get { return CheckFlagsAnd(OneNoteObjectFlags.Invalid); }
        }

        // Changes

        public void AcceptChanges()
        {
            CheckValid();

            if (_properties != null)
            {
                foreach (var io in (from prop in GetProperties()
                                    select _properties.GetValue(prop)).OfType<OneNoteObject>())
                {
                    io.AcceptChanges();
                }
            }

            if (_innerObjects != null)
            {
                _innerObjects.AcceptChanges();
            }

            HasConflicts = false;
        }

        public void RevertChanges()
        {
            CheckValid();

            if (_properties != null)
            {
                foreach (OneNoteProperty prop in GetProperties())
                {
                    OneNoteObject value = (_properties.GetValue(prop) as OneNoteObject);

                    if (value != null)
                    {
                        value.RevertChanges();
                    }

                    _properties.ClearValue(prop);
                }
            }

            if (_innerObjects != null)
            {
                _innerObjects.RevertChanges();
            }

            HasConflicts = false;
        }

        // Server view

        public static TObject GetServerView<TObject>(TObject source) where TObject : OneNoteObject
        {
            Contract.RequiresNotNullAndValid(source, "source");

            TObject result = (TObject)(source._server);

            if (result == null)
            {
                result = OneNoteObjectFactory.PrepareClone<TObject>(source);
                
                result._flags = OneNoteObjectFlags.IsServerView;

                if (source._properties != null)
                {
                    result._properties = source._properties.GetServerView(result);
                }
                
                source._server = result;
            }

            return result;
        }

        // Invalidating

        internal void CheckValid()
        {
            if (IsInvalid)
            {
                throw FrameworkExceptions.ObjectInvalid();
            }

            if (CheckFlagsAnd(OneNoteObjectFlags.IsServerView))
            {
                throw FrameworkExceptions.ObjectIsServerView();
            }
        }

        internal void CheckValidOrServerView()
        {
            if (!CheckFlagsAnd(OneNoteObjectFlags.IsServerView))
            {
                CheckValid();
            }
        }

        internal void MarkInvalid()
        {
            _flags = OneNoteObjectFlags.Invalid;
            _properties = null;

            if (_innerObjects != null)
            {
                _innerObjects.MarkInvalid();
                _innerObjects = null;
            }

            MarkInvalidOverride();
        }

        internal virtual void MarkInvalidOverride()
        {
        }

        // Writing

        internal void WriteTo(XContainer parent)
        {
            XElement element = OneNoteSchema.CreateElement(this);

            if (CheckFlagsAnd(OneNoteObjectFlags.Dirty))
            {
                WritePropertiesTo(element);
                SetFlags(false, OneNoteObjectFlags.Dirty);
            }

            if (CheckFlagsAnd(OneNoteObjectFlags.ChildrenDirty))
            {
                WriteChildrenTo(element);
                SetFlags(false, OneNoteObjectFlags.ChildrenDirty);
            }

            parent.Add(element);
        }

        internal virtual void WritePropertiesTo(XElement element)
        {
            OneNoteSchema.SerializeAllProperties(this, element);
        }

        internal virtual void WriteChildrenTo(XElement element)
        {
            if (_innerObjects != null)
            {
                _innerObjects.WriteTo(element);
            }
        }

        // Properties

        internal virtual void OnPropertyChanged(OneNoteProperty property, Object newValue, Object oldValue)
        {
        }

        internal virtual void OnPropertyRequested(OneNoteProperty property)
        {
        }

        internal void ClearValue(OneNoteProperty property)
        {
            Properties.ClearValue(property);
        }

        internal void DeserializeValue(OneNoteProperty property, Object value)
        {
            Properties.DeserializeValue(property, value);
        }

        internal object GetValue(OneNoteProperty property)
        {
            return Properties.GetValue(property);
        }

        internal T GetValue<T>(OneNoteProperty property)
        {
            object result = GetValue(property);

            if (result is T)
            {
                return (T)(result);
            }

            return default(T);
        }

        internal void SetValue(OneNoteProperty property, Object value)
        {
            Properties.SetValue(property, value);
        }

        internal OneNoteProperty[] GetProperties()
        {
            return OneNoteProperty.GetProperties(this);
        }

        internal OneNoteProperty[] GetDirtyProperties()
        {
            return Properties.GetDirtyProperties();
        }

        private OneNoteObjectProperties Properties
        {
            get
            {
                CheckValidOrServerView();

                if (_properties == null)
                {
                    _properties = new OneNoteObjectProperties(this);
                }

                return _properties;
            }
        }

        // Flags

        internal bool IsDeserializing
        {
            get { return CheckFlagsAnd(OneNoteObjectFlags.Deserializing); }
        }

        internal bool CheckFlagsAnd(OneNoteObjectFlags flags)
        {
            return (_flags & flags) == flags;
        }

        internal bool CheckFlagsOr(OneNoteObjectFlags flags)
        {
            if (flags == OneNoteObjectFlags.None)
            {
                return true;
            }

            return (_flags & flags) != OneNoteObjectFlags.None;
        }

        internal void SetFlags(bool value, OneNoteObjectFlags flags)
        {
            _flags = value ? (_flags | flags) : (_flags & ~flags);
        }
    }
}
